﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Luminal
{
    public sealed partial class CommandQueue : LuminalObject
    {
        #region Read Image
        public Event EnqueueReadImage<T>(MemoryObject image, bool blocking, T[] data, params Event[] waitList) where T : struct
        {
            if (image.Type == MemoryObjectType.Image2D)
                return EnqueueReadImage<T>(image, blocking, new ImageRegion2D(0, 0, image.Width, image.Height), data, waitList);
            else if (image.Type == MemoryObjectType.Image3D)
                return EnqueueReadImage<T>(image, blocking, new ImageRegion3D(0, 0, 0, image.Width, image.Height, image.Depth), data, waitList);
            else
                throw new ArgumentException("The specified memory object is not an image.", "image");
        }

        public Event EnqueueReadImage<T>(MemoryObject image, bool blocking, ImageRegion2D region, T[] data, params Event[] waitList) where T : struct
        {
            #region Verification
            // Self Verification
            if (Disposed)
                throw new ObjectDisposedException(GetType().FullName);

            // Image Verification
            if (image == null)
                throw new ArgumentNullException("image");
            if (image.Disposed)
                throw new ObjectDisposedException("image");
            if (image.Type == MemoryObjectType.Buffer)
                throw new ArgumentException("The specified memory object is not an image.", "image");

            // Region Verification
            if (region.X < 0)
                throw new ArgumentOutOfRangeException("region", "The x coordinate of the region must be greater or equal to zero.");
            if (region.Y < 0)
                throw new ArgumentOutOfRangeException("region", "The y coordinate of the region must be greater or equal to zero.");
            if (region.Width < 0)
                throw new ArgumentOutOfRangeException("region", "The width of the region must be greater than zero.");
            if (region.Height < 0)
                throw new ArgumentOutOfRangeException("region", "The height of the region must be greater than zero.");

            // Data Verification
            if (data == null)
                throw new ArgumentNullException("data");
            if (data.Length == 0)
                throw new ArgumentException("Parameter must contain at least one element.", "data");

            // Wait List Verification
            if (waitList != null && waitList.Any(n => n.Disposed))
                throw new ObjectDisposedException("waitList");
            if (waitList != null && waitList.Length == 0)
                waitList = null;
            #endregion

            int tSize = Marshal.SizeOf(typeof(T));
            IntPtr evt = default(IntPtr);

            IntPtr[] waitListPtrs = (waitList == null || waitList.Length == 0) ? null : Util.ExtractPointers<Event>(waitList);

            SizeT[] origin = new SizeT[]
            {
                region.X,
                region.Y,
                0,
            };

            SizeT[] size = new SizeT[]
            {
                region.Width,
                region.Height,
                1,
            };

            Util.Pin(data, ptr =>
            {
                Util.ThrowError(
                    "Could not queue operation.",
                    clEnqueueReadImage(InternalPointer, image.InternalPointer, blocking ? 1u : 0u, origin, size, 0, 0, ptr, (uint)waitList.Length, waitListPtrs, out evt));
            });

            return Event.FromHandle(evt);
        }
        #endregion

        #region Write Image
        public Event EnqueueWriteImage<T>(MemoryObject image, bool blocking, T[] data, params Event[] waitList) where T : struct
        {
            if (image.Type == MemoryObjectType.Image2D)
                return EnqueueWriteImage<T>(image, blocking, new ImageRegion2D(0, 0, image.Width, image.Height), data, waitList);
            else if (image.Type == MemoryObjectType.Image3D)
                return EnqueueWriteImage<T>(image, blocking, new ImageRegion3D(0, 0, 0, image.Width, image.Height, image.Depth), data, waitList);
            else
                throw new ArgumentException("The specified memory object is not an image.", "image");
        }

        public Event EnqueueWriteImage<T>(MemoryObject image, bool blocking, ImageRegion2D region, T[] data, params Event[] waitList) where T : struct
        {
            #region Verification
            // Self Verification
            if (Disposed)
                throw new ObjectDisposedException(GetType().FullName);

            // Image Verification
            if (image == null)
                throw new ArgumentNullException("image");
            if (image.Disposed)
                throw new ObjectDisposedException("image");
            if (image.Type == MemoryObjectType.Buffer)
                throw new ArgumentException("The specified memory object is not an image.", "image");

            // Region Verification
            if (region.X < 0)
                throw new ArgumentOutOfRangeException("region", "The x coordinate of the region must be greater or equal to zero.");
            if (region.Y < 0)
                throw new ArgumentOutOfRangeException("region", "The y coordinate of the region must be greater or equal to zero.");
            if (region.Width < 0)
                throw new ArgumentOutOfRangeException("region", "The width of the region must be greater than zero.");
            if (region.Height < 0)
                throw new ArgumentOutOfRangeException("region", "The height of the region must be greater than zero.");

            // Data Verification
            if (data == null)
                throw new ArgumentNullException("data");
            if (data.Length == 0)
                throw new ArgumentException("Parameter must contain at least one element.", "data");

            // Wait List Verification
            if (waitList != null && waitList.Any(n => n.Disposed))
                throw new ObjectDisposedException("waitList");
            if (waitList != null && waitList.Length == 0)
                waitList = null;
            #endregion

            int tSize = Marshal.SizeOf(typeof(T));
            IntPtr evt = default(IntPtr);

            IntPtr[] waitListPtrs = (waitList == null || waitList.Length == 0) ? null : Util.ExtractPointers<Event>(waitList);

            SizeT[] origin = new SizeT[]
            {
                region.X,
                region.Y,
                0,
            };

            SizeT[] size = new SizeT[]
            {
                region.Width,
                region.Height,
                1,
            };

            Util.Pin(data, ptr =>
            {
                Util.ThrowError(
                    "Could not queue operation.",
                    clEnqueueWriteImage(InternalPointer, image.InternalPointer, blocking ? 1u : 0u, origin, size, 0, 0, ptr, waitList == null ? 0 : (uint)waitList.Length, waitListPtrs, out evt));
            });

            return Event.FromHandle(evt);
        }
        #endregion

        #region Copy Image
        public Event EnqueueCopyImage(MemoryObject source, MemoryObject destination, params Event[] waitList)
        {
            if (source.Type == MemoryObjectType.Buffer)
                throw new ArgumentException("The specified memory object is not an image.", "source");
            if (destination.Type == MemoryObjectType.Buffer)
                throw new ArgumentException("The specified memory object is not an image.", "destination");

            if (source.Type == MemoryObjectType.Image2D || destination.Type == MemoryObjectType.Image2D)
                return EnqueueCopyImage(source, destination, new ImageRegion2D(0, 0, Math.Min(source.Width, destination.Width), Math.Min(source.Height, destination.Height)), waitList);
            else // source/desination.Type == MemoryObjectType.Image3D
                return EnqueueCopyImage(source, destination, new ImageRegion3D(0, 0, 0, Math.Min(source.Width, destination.Width), Math.Min(source.Height, destination.Height), Math.Min(source.Depth, destination.Depth)), waitList);
        }

        public Event EnqueueCopyImage(MemoryObject source, MemoryObject destination, ImageRegion2D region, params Event[] waitList)
        {
            return EnqueueCopyImage(source, destination, region, region, waitList);
        }

        public Event EnqueueCopyImage(MemoryObject source, MemoryObject destination, ImageRegion2D sourceRegion, ImageRegion2D destinationRegion, params Event[] waitList)
        {
            #region Verification
            // Self Verification
            if (Disposed)
                throw new ObjectDisposedException(GetType().FullName);

            // Source Verification
            if (source == null)
                throw new ArgumentNullException("source");
            if (source.Disposed)
                throw new ObjectDisposedException("source");
            if (source.Type == MemoryObjectType.Buffer)
                throw new ArgumentException("The specified memory object is not an image.", "image");

            // Destination Verification
            if (destination == null)
                throw new ArgumentNullException("destination");
            if (destination.Disposed)
                throw new ObjectDisposedException("destination");
            if (destination.Type == MemoryObjectType.Buffer)
                throw new ArgumentException("The specified memory object is not an image.", "image");

            // Source Region Verification
            if (sourceRegion.X < 0)
                throw new ArgumentOutOfRangeException("sourceRegion", "The x coordinate of the region must be greater or equal to zero.");
            if (sourceRegion.Y < 0)
                throw new ArgumentOutOfRangeException("sourceRegion", "The y coordinate of the region must be greater or equal to zero.");
            if (sourceRegion.Width < 0)
                throw new ArgumentOutOfRangeException("sourceRegion", "The width of the region must be greater than zero.");
            if (sourceRegion.Height < 0)
                throw new ArgumentOutOfRangeException("sourceRegion", "The height of the region must be greater than zero.");
            
            // Destination Region Verification
            if (destinationRegion.X < 0)
                throw new ArgumentOutOfRangeException("destinationRegion", "The x coordinate of the region must be greater or equal to zero.");
            if (destinationRegion.Y < 0)
                throw new ArgumentOutOfRangeException("destinationRegion", "The y coordinate of the region must be greater or equal to zero.");
            if (destinationRegion.Width < 0)
                throw new ArgumentOutOfRangeException("destinationRegion", "The width of the region must be greater than zero.");
            if (destinationRegion.Height < 0)
                throw new ArgumentOutOfRangeException("destinationRegion", "The height of the region must be greater than zero.");
            
            // Source/Destination Size Verification
            if (sourceRegion.Width != destinationRegion.Width || sourceRegion.Height != destinationRegion.Height)
                throw new ArgumentException("Destination region size must match that of the source region size.", "destinationRegion");

            // Wait List Verification
            if (waitList != null && waitList.Any(n => n.Disposed))
                throw new ObjectDisposedException("waitList");
            if (waitList != null && waitList.Length == 0)
                waitList = null;
            #endregion

            IntPtr evt = default(IntPtr);

            IntPtr[] waitListPtrs = (waitList == null || waitList.Length == 0) ? null : Util.ExtractPointers<Event>(waitList);

            SizeT[] sourceOffset =
            {
                sourceRegion.X,
                sourceRegion.Y,
                0,
            };

            SizeT[] destinationOffset =
            {
                destinationRegion.X,
                destinationRegion.Y,
                0,
            };

            SizeT[] size = 
            {
                sourceRegion.Width,
                sourceRegion.Height,
                1,
            };

            Util.ThrowError(
                "Could not queue operation.",
                clEnqueueCopyImage(InternalPointer, source.InternalPointer, destination.InternalPointer, sourceOffset, destinationOffset, size, (uint)waitList.Length, waitListPtrs, out evt));

            return Event.FromHandle(evt);
        }
        #endregion

        // TODO: Implement Image2D Map Methods
    }
}
